#include "netforward.h"
#include <stdio.h>
#include <stdlib.h>
#include "cmap.h"
#include "uv_common.h"
#include <time.h>
#include "udp.h"
#include "uv_thread.h"
#include "thrqueue.h"
/*
comming ip:port ---->  local port----- [proxy]-----> remote ip :port

*/

#define CLIENT_CHECK_INTERVAL   60*2 //seconds
#define CLIENT_TIMEOUT          60*4

typedef struct NetForwardAddr_s {
    NET_TYPE nettype; //tcp or udp
    struct sockaddr addr;
}NetForwardAddr;

typedef struct ForwardAddrPair_s {
    NetForwardAddr localAddr;
    NetForwardAddr remoteAddr;
}ForwardAddrPair;

typedef struct UDPConnectionPair_s {
    NetForwardAddr comingAddr;
    NetForwardAddr peerAddr;
    udp_t serverHandle;
    udp_t peerHandle;
    time_t lastSeenTime;
}UDPConnectionPair;


typedef struct NetForward_s {
    CMAP *configMap;     //key: NetForwardAddr //ForwardAddrPair
    CMAP *connectionMap; // key : comingAddr value: UDPConnectionPair
}NetForward;


static NetForward *g_netForward = NULL;
static uv_thread_t g_guard_tid;

void netforward_init()
{
    if (g_netForward == NULL) {
        g_netForward = (NetForward *)calloc(1, sizeof(NetForward));
        g_netForward->configMap = cmap_new();
        g_netForward->connectionMap = cmap_new();
    }

}

int netforward_add_config(const char *ip, uint16_t port, const char *remoteip, uint16_t remoteport, NET_TYPE mode)
{
    int ret = 0;
    if (g_netForward) {
        ForwardAddrPair *nfaddrpair = (ForwardAddrPair *)malloc(sizeof(ForwardAddrPair));
        nfaddrpair->localAddr.nettype = mode;
        uv_ip4_addr(ip, port, (struct sockaddr_in *)&nfaddrpair->localAddr.addr);

        nfaddrpair->remoteAddr.nettype = mode;
        uv_ip4_addr(remoteip, remoteport, (struct sockaddr_in *)&nfaddrpair->remoteAddr.addr);
        if (cmap_put2(g_netForward->configMap, &nfaddrpair->localAddr, sizeof(nfaddrpair->localAddr), nfaddrpair, free)) {
            ret = -1;
            printf("%s\n", "add failed");
        }

    } else {
        ret = -1;
    }

    return ret;
}

int netforward_reset_config()
{
    int ret = 0;
    if (g_netForward) {

    } else {
        ret = -1;
    }
    return ret;
}

int netforward_get_config()
{
    int ret = 0;
    if (g_netForward) {

    } else {
        ret = -1;
    }
    return ret;
}


static  void common_udp_send_cb(SendReq_t * req, int status)
{
    free(req->buf.base);
    free(req);
}

static void peerrecvcb(RecvReq_t *recv_req, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned flags)
{
    if (nread > 0) {
        udp_t *handle = recv_req->handle;
        UDPConnectionPair *cp = (UDPConnectionPair *)((udp_t *)handle)->data;
        SendReq_t *req = (SendReq_t *)malloc(sizeof(SendReq_t));
        req->buf.base = buf->base;
        udp_send(req, &cp->serverHandle, buf->base, nread, &cp->comingAddr.addr, common_udp_send_cb);
    } else { //peer can not be reached

    }
}

static void onserver_received(RecvReq_t* _req, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned flags)
{
    udp_t *handle = _req->handle;
    if (nread > 0) {
        const struct sockaddr *remoteaddr = (const struct sockaddr *)handle->data;

        //find peer from <comming -> peer> map
        //if has peer just send
        //if no peer we create one and send;
        //   * set peer udp_t data = comming addr
        //   * just send to addr when peer recv data
        //

        NetForwardAddr commingaddr = { NETTYPE_UDP, *addr };

        udp_t *clientproxy = NULL;

        UDPConnectionPair *cp = cmap_get(g_netForward->connectionMap, &commingaddr, sizeof(commingaddr));
        if (cp == NULL) {
            cp = (UDPConnectionPair *)malloc(sizeof(UDPConnectionPair));
            cp->serverHandle = *handle;
            cp->comingAddr = commingaddr;
            cp->peerAddr.nettype = NETTYPE_UDP;
            cp->peerAddr.addr = *remoteaddr;
            udp_createhandler(&cp->peerHandle, NULL, 0);

            cp->peerHandle.data = cp;
            udp_recv_start(&cp->peerHandle, peerrecvcb);

            cmap_put2(g_netForward->connectionMap, &commingaddr, sizeof(commingaddr), cp, free);
        }
        cp->lastSeenTime = time(NULL);
        clientproxy = &cp->peerHandle;
        SendReq_t *req = (SendReq_t *)malloc(sizeof(SendReq_t));
        req->buf = *buf;
        udp_send(req, clientproxy, buf->base, nread, remoteaddr, common_udp_send_cb);
    } else {
        printf("%s:nread=%zd\n", __func__, nread);
    }
}

static int startUDPServer(const struct sockaddr_in *addr, struct sockaddr_in *remoteaddr)
{
    int ret = 0;
    udp_init();
    udp_t *udp = (udp_t *)malloc(sizeof(udp_t));
    udp->data = remoteaddr;
    udp_createhandler(udp, (struct sockaddr *)addr, sizeof(*addr));
    udp_recv_start(udp, onserver_received);
    return ret;
}

static void actualStart(void *key, size_t keylen, void *value, void *parm)
{
    int ret = 0;
    ForwardAddrPair * config = (ForwardAddrPair * )value;
    if (config->localAddr.nettype == NETTYPE_UDP) {
        startUDPServer((struct sockaddr_in*)&config->localAddr.addr, (struct sockaddr_in*)&config->remoteAddr.addr);
    }
    return;
}



static void checkTimeout(void *key, size_t keylen, void *value, void *parm)
{
    UDPConnectionPair * conninfo = (UDPConnectionPair *)value;
    if (labs(time(NULL) - conninfo->lastSeenTime) > CLIENT_TIMEOUT) {
        udp_shutdown(NULL, &conninfo->peerHandle, NULL);
        udp_close(&conninfo->peerHandle);
        cmap_rm(g_netForward->connectionMap, key, keylen);
    }
}

static void udp_client_guard(void *parm)
{
   printf("%s E\n", __func__);
   while (1) {
       sleep(CLIENT_CHECK_INTERVAL);
       cmap_foreach(g_netForward->connectionMap, checkTimeout, NULL);
   }
}


static void doStop(void *key, size_t keylen, void *value, void *parm)
{
    UDPConnectionPair * conninfo = (UDPConnectionPair * )value;
    udp_shutdown(NULL, &conninfo->peerHandle, NULL);
    udp_shutdown(NULL, &conninfo->serverHandle, NULL);
    udp_close(&conninfo->peerHandle);
    udp_close(&conninfo->serverHandle);
    cmap_rm(g_netForward->connectionMap, key, keylen);
}

int netforward_start()
{
    int ret = 0;
    if (g_netForward) {
        cmap_foreach(g_netForward->configMap, actualStart, NULL);

        uv_thread_create(&g_guard_tid, udp_client_guard, NULL);
    } else {
        ret = -1;
    }
    printf("%s X %d\n", __func__, ret);
    return ret;
}


int netforward_stop()
{
    printf("%s E\n", __func__);
    int ret = 0;
    if (g_netForward) {
        cmap_foreach(g_netForward->connectionMap, doStop, NULL);
    } else {
        ret = -1;
    }
    return ret;
}





#ifndef NDEBUG


void tst()
{
        ///////////////
    netforward_init();
    netforward_add_config("0.0.0.0", 1234, "127.0.0.1", 1235, NETTYPE_UDP);

    netforward_start();



    getchar();
    netforward_stop();

}
#endif